package org.fuys.coder.domain.model.service.impl;

import org.fuys.coder.infrastructure.model.dto.UserModelDTO;
import org.fuys.coder.domain.config.service.impl.CoderSecurityConfig;
import org.fuys.coder.common.entity.Pair;
import org.fuys.coder.domain.config.service.impl.CoderRecommendConfig;
import org.fuys.coder.common.constants.RedisConstants;
import org.fuys.coder.common.util.DateUtil;
import org.fuys.coder.infrastructure.util.RedisUtil;
import org.fuys.coder.domain.model.repository.IUserModelRepository;
import org.fuys.coder.domain.model.service.IUserModelService;
import org.fuys.coder.domain.substance.model.req.operation.SubstanceOperationReq;
import org.fuys.coder.domain.substance.model.vo.SubstanceOperationTypeVO;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @projectName: fuys-low-coder
 * @package: org.fuys.coder.domain.model.service.impl
 * @className: UserModelService
 * @author: WangJing
 * @description: 用户模型服务实现类
 * @date: 2024/6/26 17:17
 * @version: 1.0
 */
@Service
public class UserModelService implements IUserModelService {

    @Resource
    private IUserModelRepository userModelRepository;

    @Resource
    private CoderRecommendConfig config;

    @Resource
    private CoderSecurityConfig securityConfig;


    @Override
    public List<Pair<Integer,Double>> getUserLikeCategoryIds(Integer userId) {
        final String redisKey = RedisConstants.REDIS_FIELD_USER + RedisConstants.REDIS_USE_MODEL +
                RedisConstants.REDIS_SPLIT + userId;
        final List<Object> cacheWeightPair = userModelRepository.getCacheWeightPair(redisKey);
        if(!ObjectUtils.isEmpty(cacheWeightPair)){
            return cacheWeightPair.stream().map(item -> {
                return (Pair<Integer, Double>) item;
            }).collect(Collectors.toList());
        }
        final List<UserModelDTO> roughModel = userModelRepository.getRoughModel(userId);
        if(ObjectUtils.isEmpty(roughModel)){
            return null;
        }
        return roughModel.stream().map(item-> {
                    return new Pair<Integer, Double>(item.getCategoryId(),item.getCategoryWeight());
                }
        ).collect(Collectors.toList());
    }

    //要注意的是 我们这里仅仅只会更新redis中的值
    @Override
    @Async
    public void addCommonUserWeight(SubstanceOperationReq req) {
        //todo 采用Redis的Zset是否会更加理想？
        final Integer userId = req.getUserId();
        String redisKey= RedisConstants.REDIS_FIELD_USER+RedisConstants.REDIS_USE_MODEL
                +RedisConstants.REDIS_SPLIT+ userId;
        List<Pair<Integer, Double>> collect = getRedisWeightPair(redisKey);

        List<Integer> idList=collect.stream().map(Pair::getLeft).collect(Collectors.toList());
        if(req.getOperationType().equals(SubstanceOperationTypeVO.THUMB.getIdx())){
            addRedisWeight(req.getCategoryId(),config.getThumbWeight(), userId,redisKey, collect, idList);
        }else if(req.getOperationType().equals(SubstanceOperationTypeVO.FAVORITES.getIdx())){
            addRedisWeight(req.getCategoryId(),config.getFavWeight(), userId,redisKey, collect, idList);
        }else if(req.getOperationType().equals(SubstanceOperationTypeVO.UNLIKE.getIdx())){
            addRedisWeight(req.getCategoryId(),config.getUnlikeWeight(), userId,redisKey, collect, idList);
        }
    }

    @NotNull
    private List<Pair<Integer, Double>> getRedisWeightPair(String redisKey) {
        //list
        final List<Object> redisRecord = userModelRepository.getCacheWeightPair(redisKey);
        List<Pair<Integer, Double>> collect = null;
        if (ObjectUtils.isEmpty(redisRecord)) {
            collect = new ArrayList<>();
        } else {
            collect = redisRecord.stream().map(item -> {
                return ((Pair<Integer, Double>) item);
            }).collect(Collectors.toList());
        }
        return collect;
    }

    private void addRedisWeight(Integer categoryId,Double weight,Integer userId
            , String redisKey, List<Pair<Integer, Double>> collect, List<Integer> idList) {
        if(idList.contains(categoryId)){
            //如果包含此id 则累加权重 否则直接添加到末尾
            collect.stream().filter(item->item.getLeft().equals(categoryId)).forEach(item->{
                item.setRight(item.getRight()+weight);
            });
            for(int i= collect.size()-1;i>=0;i--){
                if(collect.get(i).getRight()<=0.0){
                    collect.remove(i);
                }
            }
            final int size = collect.size();
            //防止数据膨胀
            collect.forEach(item->{
                item.setRight(item.getRight()/size);
            });
            userModelRepository.cacheUserWeights(redisKey,collect);
        }else{
            //todo 或许该单独维护一个不喜欢列表
            if(weight<0.0)
                return;
            if(categoryId!=null)
                userModelRepository.addCacheUserWeights(redisKey,new Pair<>(categoryId,weight));
        }
        String redisUserModelKey=RedisConstants.REDIS_FIELD_USER
                +RedisConstants.REDIS_USE_MODEL+RedisConstants.REDIS_SPLIT+RedisConstants.REDIS_DESC_EXPIRED;
        userModelRepository.addModelExpired(redisUserModelKey,userId, (double) DateUtil.addTimeFromCur(securityConfig.getUserModelExpire()
                , TimeUnit.DAYS).getTime());
    }

    @Override
    public void addVipUserWeight(SubstanceOperationReq req) {

    }

    @Override
    public void enduranceUserModel() {
        String redisKey=RedisConstants.REDIS_FIELD_USER
                +RedisConstants.REDIS_USE_MODEL+RedisConstants.REDIS_SPLIT+RedisConstants.REDIS_DESC_EXPIRED;
        String userKeyPre=RedisConstants.REDIS_FIELD_USER+RedisConstants.REDIS_USE_MODEL+
                RedisConstants.REDIS_SPLIT;
        final Set<ZSetOperations.TypedTuple<Object>> typedTuples = userModelRepository.getExpiredModel(redisKey,-1);
        final long clear = DateUtil.addTimeFromCur(securityConfig.getUserModelUpdate(), TimeUnit.DAYS).getTime();
        for (ZSetOperations.TypedTuple<Object> typedTuple : typedTuples) {
            final Double score = typedTuple.getScore();
            if(score==null){
                //todo 没有设置score的情况暂不考虑
                continue;
            }
            final long time = score.longValue();
            if(clear>=time){
                //说明剩余期限不足 需要持久化
                final Object value = typedTuple.getValue();
                if(value==null){
                    continue;
                }
                final String r_userId = value.toString();
                List<Pair<Integer, Double>> collect = getRedisWeightPair(userKeyPre+r_userId);
                userModelRepository.saveUserModel(Integer.valueOf(r_userId),collect);
            }
        }

    }
}
